package com.dly.blog.service;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.dly.blog.content.RedisContent;
import com.dly.blog.domain.Token;
import com.dly.blog.domain.User;
import com.dly.blog.domian.ro.*;
import com.dly.blog.domian.vo.AdminLoginVO;
import com.dly.blog.exception.AuthExceptionEnum;
import com.dly.blog.exception.CommonExceptionEnum;
import com.dly.blog.provider.user.TokenProvider;
import com.dly.blog.provider.user.UserProvider;
import com.dly.blog.security.UserAuth;
import com.dly.blog.domian.vo.LoginVO;
import com.dly.blog.token.AccountToken;
import com.dly.blog.token.AdminToken;
import com.dly.blog.token.EmailToken;
import com.dly.blog.token.WxLoginToken;
import com.dly.blog.utile.JwtService;
import com.dly.blog.utile.RedisUtils;
import com.dly.blog.utile.SecurityUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.Objects;
import java.util.Optional;

/**
 * @Author: DaiLiYu
 * @Date: 2023/7/10 23:07
 */
@Service
@AllArgsConstructor
@Slf4j
public class AuthenticationService {

    private final ProviderManager providerManager;

    private final TokenProvider tokenProvider;

    private final JwtService jwtService;

    private final PasswordEncoder passwordEncoder;

    private final RedisUtils redisUtils;

    private final WeChatService weChatService;

    private final UserProvider userProvider;

    public LoginVO wxLogin(WxLoginRO wxLoginRO){
        final String openId = weChatService.getOpenId(wxLoginRO.getCode());
        if(StrUtil.isBlank(openId)){
            throw CommonExceptionEnum.WX_OPENID_NULL.getCommonException();
        }
        Authentication authenticate = providerManager.authenticate(new WxLoginToken(openId));
        //登入成功把当前登入用户放入redis
        redisUtils.addSet(RedisContent.ONLINE, ((UserAuth) authenticate.getPrincipal()).getId());
        return builderLoginVO((UserAuth) authenticate.getPrincipal());
    }


    public LoginVO register(UserRegisterRO userRegisterRO){

        //获取验证码
        String captchaCode  = redisUtils.getCacheObject(RedisContent.CAPTCHA_EMAIL + userRegisterRO.getEmail());

        if(Objects.isNull(captchaCode) || !captchaCode.equals(userRegisterRO.getCode())){
            throw AuthExceptionEnum.CAPTCHA_EXPIRED.getAuthException();
        }

        final var user = User.builder()
                .userAccount(userRegisterRO.getUserAccount())
                .userName(userRegisterRO.getUserName())
                .userEmail(userRegisterRO.getEmail())
                .password(passwordEncoder.encode(userRegisterRO.getPassword()))
                .build();
        userProvider.insert(user);

        UserAuth userAuth = new UserAuth();
        BeanUtils.copyProperties(user, userAuth);
        return builderLoginVO(userAuth);
    }

    /**
     * 校验验证码
     * @param uuid
     * @param code
     */
    private void checkCaptcha(String uuid, String code){
        String cacheObject = redisUtils.getCacheObject(RedisContent.CAPTCHA+uuid);
        if(ObjUtil.isNull(cacheObject) || !cacheObject.equals(code)){
            throw  AuthExceptionEnum.CAPTCHA_EXPIRED.getAuthException();
        }
    }

    /**
     * 认证相关
     * @param loginRO
     * @return
     */
    public LoginVO authenticate(LoginRO loginRO) {
        checkCaptcha(loginRO.getUuid(), loginRO.getCode());
        //登入
        Authentication authenticate = loginRO.getLoginType()
                .getAuthentication()
                .apply(loginRO);
        if(authenticate == null){
            throw AuthExceptionEnum.LOGIN_ERROR.getAuthException();
        }

        //登入成功把当前登入用户放入redis
        redisUtils.addSet(RedisContent.ONLINE, ((UserAuth) authenticate.getPrincipal()).getId());

        //原来的token失效
        revokeAllUserTokens(((UserAuth) authenticate.getPrincipal()).getId());
        return builderLoginVO((UserAuth) authenticate.getPrincipal());
    }

    public LoginVO builderLoginVO(UserAuth userAuth){
        //生成 jwtToken
        var jwtToken = jwtService.generateToken(userAuth);
        //生成 刷新token
        var refreshToken = jwtService.generateRefreshToken(userAuth);
        saveUserToken(userAuth, jwtToken);
        saveUserToken(userAuth, refreshToken);
        return LoginVO.builder()
                .accessToken(jwtToken)
                .refreshToken(refreshToken)
                .build();
    }

    public AdminLoginVO builderAdminLoginVO(UserAuth userAuth){
        //生成 jwtToken
        var jwtToken = jwtService.generateToken(userAuth);
        //生成 刷新token
        var refreshToken = jwtService.generateRefreshToken(userAuth);
        saveUserToken(userAuth, jwtToken);
        saveUserToken(userAuth, refreshToken);
        return AdminLoginVO.builder()
                .token(jwtToken)
                .refreshToken(refreshToken)
                .build();
    }

    private void revokeAllUserTokens(String id) {
       tokenProvider.updateBathValid(id);
    }

    private void saveUserToken(UserAuth user, String jwtToken) {
        var token = Token.builder()
                .userId(user.getId())
                .token(jwtToken)
                .tokenType("BEARER")
                .isExpired(0)
                .isRevoked(0)
                .build();
        tokenProvider.insert(token);
    }

    public LoginVO refreshToken(String refreshToken) throws AuthExceptionEnum.AuthException {
        log.info("user  refreshToken start");
        final String userId;
        Boolean aBoolean = Optional.ofNullable(tokenProvider.selectByToken(refreshToken))
                .map(t -> Objects.equals(t.getIsExpired(), 0) && Objects.equals(t.getIsRevoked(), 0))
                .orElse(false);
        if(!aBoolean){
            throw AuthExceptionEnum.REFER_EXPIRED.getAuthException();
        }
        //获取用户id
        try {
            userId = jwtService.extractUsername(refreshToken);
        }catch (Exception e){
            throw  AuthExceptionEnum.REFER_EXPIRED.getAuthException();
        }
        if( userId != null ){
            User user = userProvider.selectById(userId);
            UserAuth userAuth = cover(user);
            if(jwtService.isTokenValid(refreshToken, userAuth)){
                String accessToken = jwtService.generateToken(userAuth);
                String refreshTokenNow = jwtService.generateRefreshToken(userAuth);
                revokeAllUserTokens(user.getId());
                saveUserToken(userAuth, accessToken);
                saveUserToken(userAuth, refreshTokenNow);
                log.info("user  refreshToken stop");
                return LoginVO.builder()
                        .accessToken(accessToken)
                        .refreshToken(refreshTokenNow)
                        .build();
            } else {
              throw AuthExceptionEnum.LOGIN_ERROR.getAuthException();
            }
        } else {
            throw AuthExceptionEnum.USER_NO.getAuthException();
        }
    }

    public UserAuth cover(User user){
        UserAuth userAuth = new UserAuth();
        userAuth.setId(user.getId());
        return userAuth;
    }

    public void logout(){
        String userId = SecurityUtils.getUserId();
        revokeAllUserTokens(userId);
        redisUtils.removeSet(RedisContent.ONLINE, userId);
    }

    public AdminLoginVO adminLogin(AdminRO adminRO) {
        //登入
        Authentication authenticate = providerManager.authenticate(new AdminToken(adminRO.getUsername(), adminRO.getPassword()));
        if(authenticate == null){
            throw AuthExceptionEnum.LOGIN_ERROR.getAuthException();
        }
        //登入成功把当前登入用户放入redis
        redisUtils.addSet(RedisContent.ADMIN_ONLINE, ((UserAuth) authenticate.getPrincipal()).getId());

        //原来的token失效
        revokeAllUserTokens(((UserAuth) authenticate.getPrincipal()).getId());
        return builderAdminLoginVO((UserAuth) authenticate.getPrincipal());
    }
}
